热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

子树|双亲_数据结构C语言《四》二叉树,堆的基本概念以及堆的相关操作实现(上)

篇首语:本文由编程笔记#小编为大家整理,主要介绍了数据结构C语言《四》二叉树,堆的基本概念以及堆的相关操作实现(上)相关的知识,希望对你有一定的参考价值。二

篇首语:本文由编程笔记#小编为大家整理,主要介绍了数据结构C语言 《四》二叉树,堆的基本概念以及堆的相关操作实现(上)相关的知识,希望对你有一定的参考价值。



二叉树


  • 1.树
    • 1.1树的概念
    • 1.2树的表示

  • 2.二叉树
    • 2.1二叉树的概念
    • 2.2二叉树的性质
    • 2.3二叉树的存储方式
    • 2.4二叉树的遍历

  • 3.堆
    • 3.1堆的概念
    • 3.2堆的实现






1.树

1.1树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树也可以这样定义:树是由根节点和若干颗子树构成的。树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的节点,所定义的关系称为父子关系。父子关系在树的节点之间建立了一个层次结构。在这种层次结构中有一个节点具有特殊的地位,这个节点称为该树的根节点,或称为树根。


  • 有一个特殊的结点,称为根结点,根节点没有前驱结点
  • 除根节点外&#xff0c;其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm&#xff0c;其中每一个集合Ti(1<&#61; i <&#61; m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱&#xff0c;可以有0个或多个后继
  • 树是递归定义的。


空集合也是树&#xff0c;称为空树。空树中没有节点&#xff1b;
孩子节点或子节点&#xff1a;一个节点含有的子树的根节点
节点的度&#xff1a;一个节点含有的子节点的个数
叶节点或终端节点&#xff1a;度为0的节点
非终端节点或分支节点&#xff1a;度不为0的节点&#xff1b;
双亲节点或父节点&#xff1a;若一个节点含有子节点&#xff0c;则这个节点称为其子节点的父节点&#xff1b;
兄弟节点&#xff1a;具有相同父节点的节点互称为兄弟节点&#xff1b;
树的度&#xff1a;最大的节点的度
节点的层次&#xff1a;从根开始定义起&#xff0c;根为第1层&#xff0c;根的子节点为第2层&#xff0c;以此类推&#xff1b;
树的高度或深度&#xff1a;树中节点的最大层次&#xff1b;
堂兄弟节点&#xff1a;双亲在同一层的节点互为堂兄弟&#xff1b;
节点的祖先&#xff1a;从根到该节点所经分支上的所有节点&#xff1b;
子孙&#xff1a;以某节点为根的子树中任一节点都称为该节点的子孙&#xff1b;
森林&#xff1a;由m(m>0)棵互不相交的树的集合称为森林&#xff1b;



1.2树的表示


  1. 孩子兄弟表示法
  2. 双亲表示法
  3. 孩子表示法
  4. 孩子兄弟表示法

2.二叉树

2.1二叉树的概念

二叉树是n个有限元素的集合&#xff0c;该集合或者为空、或者由一个称为根&#xff08;root&#xff09;的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成&#xff0c;是有序树。当集合为空时&#xff0c;称该二叉树为空二叉树。在二叉树中&#xff0c;一个元素也称作一个结点。
二叉树的特点&#xff1a;


  1. 每个结点最多有两棵子树&#xff0c;即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分&#xff0c;其子树的次序不能颠倒。

特殊的二叉树&#xff1a;


  1. 满二叉树&#xff1a;一个二叉树&#xff0c;如果每一个层的结点数都达到最大值&#xff0c;则这个二叉树就是满二叉树。也就是说&#xff0c;如果一个二叉树的层数为K&#xff0c;且结点总数是(2^k) -1 &#xff0c;则它就是满二叉树。
  2. 完全二叉树&#xff1a;完全二叉树是效率很高的数据结构&#xff0c;完全二叉树是由满二叉树而引出来的。对于深度为K的&#xff0c;有n个结点的二叉树&#xff0c;当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.2二叉树的性质


  • 性质1&#xff1a;二叉树的第i层上至多有2i-1&#xff08;i≥1&#xff09;个节点 。
  • 性质2&#xff1a;深度为h的二叉树中至多含有2h-1个节点 。
  • 性质3&#xff1a;若在任意一棵二叉树中&#xff0c;有n0个叶子节点&#xff0c;有n2个度为2的节点&#xff0c;则必有n0&#61;n2&#43;1 。
  • 性质4&#xff1a;具有n个节点的完全二叉树深为log2x&#43;1&#xff08;其中x表示不大于n的最大整数。
  • 性质5&#xff1a;若对一棵有n个节点的完全二叉树进行顺序编号&#xff08;1≤i≤n&#xff09;&#xff0c;那么&#xff0c;对于编号为i&#xff08;i≥1&#xff09;的节点&#xff1a;


当i&#61;1时&#xff0c;该节点为根&#xff0c;它无双亲节点 。
当i>1时&#xff0c;该节点的双亲节点的编号为i/2 。
若2i≤n&#xff0c;则有编号为2i的左节点&#xff0c;否则没有左节点 。
若2i&#43;1≤n&#xff0c;则有编号为2i&#43;1的右节点&#xff0c;否则没有右节点



2.3二叉树的存储方式


  • 顺序存储&#xff1a;顺序结构存储就是使用数组来存储&#xff0c;一般使用数组只适合表示完全二叉树&#xff0c;因为不是完全二叉树会有空间的浪费。二叉树顺序存储在物理上是一个数组&#xff0c;在逻辑上是一颗二叉树。
  • 链式存储&#xff1a;二叉树的链式存储结构是指&#xff0c;用链表来表示一棵二叉树&#xff0c;即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成&#xff0c;数据域和左右指针域&#xff0c;左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。

2.4二叉树的遍历

前序/中序/后序的递归结构遍历&#xff1a;是根据访问结点操作发生位置命名


  1. NLR&#xff1a;前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. LNR&#xff1a;中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中&#xff08;间&#xff09;。
  3. LRN&#xff1a;后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

简而言之


方式顺序
前序遍历根、左、右
中序遍历左、根、右
后序遍历左、右、根

由于被访问的结点必是某子树的根&#xff0c;所以N(Node&#xff09;、L(Left subtree&#xff09;和R(Right subtree&#xff09;又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。


3.堆

3.1堆的概念

如果有一个关键码的集合K &#61; k0&#xff0c;k1&#xff0c; k2&#xff0c;…&#xff0c;kn-1&#xff0c;把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中&#xff0c;并满足&#xff1a;Ki <&#61; K2i&#43;1 且 Ki<&#61; K2i&#43;2 (Ki >&#61; K2i&#43;1 且 Ki >&#61; K2i&#43;2) i &#61; 0&#xff0c;1&#xff0c;2…&#xff0c;则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆&#xff0c;根节点最小的堆叫做最小堆或小根堆。
堆的性质&#xff1a;
1.堆中某个节点的值总是不大于或不小于其父节点的值&#xff1b;
2.堆总是一棵完全二叉树。


3.2堆的实现



Heap.h


#pragma once
typedef int DataType;
//定义一个函数指针类型&#xff0c;用来选择大小堆实现方式
typedef int(*PCompare)(DataType left, DataType right);
typedef struct Heap

DataType *arr;
int capacity;
int size;
PCompare PCOM; //函数指针变量&#xff0c;来指向所有比较函数
Heap;
//小堆方式
int Less(DataType child, DataType parent);
//大堆方式
int Greater(DataType child, DataType parent);
//堆的初始化
void HeapInit(Heap *hp, DataType *arr, int size,PCompare PCOM);
//堆的插入
void HeapInsert(Heap *hp, DataType x);
//堆的删除
void HeapDelete(Heap *hp);
//获取堆顶元素
DataType HeapTop(Heap *hp);
//获取堆中有效元素个数
int HeapSize(Heap *hp);
//判空
int HeapEmpty(Heap *hp);
//堆的销毁
void HeapDestroy(Heap * hp);
//堆排序
void HeapSort(int arr[],int size);
//TOP-K问题
void PrintTopK(int* a, int n, int k);
void TestTopk();
void TestHeap();


Heap.c


#include"Heap.h"
#include
#include
#include
//小堆方式
int Less(DataType child, DataType parent)

return child < parent;

//大堆方式
int Greater(DataType child, DataType parent)

return child > parent;

//交换
void Swap(DataType *left, DataType *right)

DataType tmp &#61; *left;
*left &#61; *right;
*right &#61; tmp;

//向下调整&#xff08;大堆&#xff09;
void AdjustDown(Heap *hp, int parent)

DataType *arr &#61; hp->arr;
int size &#61; hp->size;
// 默认child标记左孩子&#xff0c;parent的右孩子可能不存在
int child &#61; parent * 2 &#43; 1;
while (child<size)

//在右孩子存在的情况下&#xff0c;找到左右孩子中最小的
if (child &#43; 1 < size && hp->PCOM(arr[child &#43; 1] , arr[child]))

child &#43;&#61; 1;

//检测双亲此时是否满足堆的特性
if (hp->PCOM(arr[child] , arr[parent]))

Swap(&arr[child], &arr[parent]);
//较大的双亲向下移动&#xff0c;可能会导致其子树不满足堆的特性&#xff0c;则还需要继续向下调整
parent &#61; child;
child &#61; parent * 2 &#43; 1;

else

return;



//向上调整&#xff08;小堆&#xff09;
void AdjustUp(Heap *hp)

DataType *arr &#61; hp->arr;
int child &#61; hp->size - 1;
int parent &#61; (child - 1) / 2;
while (child)

if (hp->PCOM(arr[child] , arr[parent]))

Swap(&arr[child], &arr[parent]);
child &#61; parent;
parent &#61; (child - 1) / 2;

else

return;



//堆的初始化
void HeapInit(Heap *hp, DataType *arr, int size, PCompare PCOM)

assert(hp);
hp->arr &#61; (DataType *)malloc(sizeof(DataType)*size);
if (hp->arr &#61;&#61; NULL)

assert(0);
return;

hp->capacity &#61; size;
//将元素逐个拷进来
for (int i &#61; 0; i < size; i&#43;&#43;)

hp->arr[i] &#61; arr[i];

hp->size &#61; size;
hp->PCOM &#61; PCOM;
//1.找倒数第一个非叶子结点
int LastNotLeafNode &#61; ((size - 1) - 1) / 2;
//2.从该节点开始一直到根结点&#xff0c;逐个往前对每个节点进行向下调整
for (int root &#61; LastNotLeafNode; root >&#61; 0; root--)

AdjustDown(hp,root);


//扩容
void CheckCapacity(Heap *hp)

if (hp->size &#61;&#61; hp->capacity)

hp->arr &#61; (DataType *)realloc(hp->arr, sizeof(DataType)*hp->size * 2);
if (hp->arr &#61;&#61; NULL)

assert(0);
return ;

hp->capacity *&#61; 2;



//堆的插入
void HeapInsert(Heap *hp, DataType x)

CheckCapacity(hp);
//1.将元素先插入有效元素之后
hp->arr[hp->size&#43;&#43;] &#61; x;
//2.新元素插入后可能会破坏堆的特性&#xff0c;则还需要对堆进行调整
AdjustUp(hp);

//堆的删除
void HeapDelete(Heap *hp)

if(HeapEmpty(hp))
return;
//将堆顶元素与堆中最后一个元素交换
Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
//将堆中有效元素个数减一
hp->size -&#61; 1;
//将堆顶元素向下调整
AdjustDown(hp, 0);

//获取堆顶元素
DataType HeapTop(Heap *hp)

assert(!HeapEmpty(hp));
return hp->arr[0];

//获取堆中有效元素个数
int HeapSize(Heap *hp)

assert(hp);
return hp->size;

//判空
int HeapEmpty(Heap *hp)

assert(hp);
return hp->size &#61;&#61; 0;

//堆的销毁
void HeapDestroy(Heap * hp)

assert(hp);
if (hp->arr)

free(hp->arr);
hp->arr &#61; NULL;
hp->capacity &#61; 0;
hp->size &#61; 0;


//堆排序的向下调整
void HeapAdjust(int arr[], int size, int parent)

int child &#61; parent * 2 &#43; 1;
while (child < size)

if (child &#43; 1 < size &&arr[child &#43; 1] > arr[child])

child &#43;&#61; 1;

if (arr[child]>arr[parent])

int tmp &#61; arr[child];
arr[child] &#61; arr[parent];
arr[parent] &#61; tmp;

parent &#61; child;
child &#61; 2 * parent &#43; 1;

else

return;



//堆排序
void HeapSort(int arr[],int size)

//1.建堆 大堆升序&#xff0c;小堆降序
int end &#61; size - 1;
for (int root &#61; (size-2) / 2; root >&#61; 0; root--)

HeapAdjust(arr, size, root);

//2.利用堆删除的思想进行排序
while (end>0)

Swap(&arr[end], &arr[0]);
HeapAdjust(arr, end, 0);
end--;


//TOP-K问题&#xff0c;利用堆来处理&#xff0c;时间复杂度O&#xff08;N&#xff09;
//找最大的元素就建小堆&#xff0c;使用前k个元素来建堆&#xff0c;剩下N-k个元素依次与堆顶元素比较
//如果该元素比堆顶元素大&#xff0c;则用该元素替换掉堆顶元素
//1.找最大的K个元素&#xff0c;同理也可以找最小的k个元素&#xff0c;这里就不一一演示了
void PrintTopK(int* a, int n, int k)

Heap hp;
HeapInit(&hp, a, k,Less);
for (int i &#61; k; i < n; i&#43;&#43;)

//每次和堆顶元素比较&#xff0c;大于堆顶元素&#xff0c;则删除堆顶元素&#xff0c;插入新的元素
if (a[i]>HeapTop(&hp))

HeapDelete(&hp);
HeapInsert(&hp, a[i]);


for (int i &#61; 0; i < k; i&#43;&#43;)

printf("%d ", HeapTop(&hp));
HeapDelete(&hp);

printf("\\n");
HeapDestroy(&hp);

void TestHeap()

int arr[] &#61; 5, 6, 2, 4, 1, 7, 3, 9, 8 ;
Heap hp;
HeapInit(&hp, arr, sizeof(arr) / sizeof(arr[0]),Greater);
printf("top&#61; %d\\n",HeapTop(&hp));
printf("size&#61; %d\\n", HeapSize(&hp));
HeapInsert(&hp, 10);
printf("top&#61; %d\\n", HeapTop(&hp));
printf("size&#61; %d\\n", HeapSize(&hp));
HeapDelete(&hp);
printf("top&#61; %d\\n", HeapTop(&hp));
printf("size&#61; %d\\n", HeapSize(&hp));
HeapDestroy(&hp);


#include"Heap.h"
int main()

//TestHeap();
//int arr1[] &#61; 5, 6, 2, 4, 1, 7, 3, 9, 8 ;
//HeapSort(arr1,sizeof(arr1)/sizeof(arr1[0]));
TestTopk();
return 0;



TOP-k问题&#xff0c;自主实现


void SwapTopk(int *left, int *right)

int tmp &#61; *left;
*left &#61; *right;
*right &#61; tmp;

//向下调整
void TOPk(int arr[], int size,int parent)

int child &#61; parent * 2 &#43; 1;
while (child < size)

if (child &#43; 1 < size &&arr[child &#43; 1] < arr[child])

child &#43;&#61; 1;

if (arr[child]<arr[parent])

SwapTopk(&arr[child], &arr[parent]);
parent &#61; child;
child &#61; 2 * parent &#43; 1;

else

return;



void PrintTopK(int* a, int n, int k)

TOPk(a, k,0);
for (int i &#61; k; i < n; i&#43;&#43;)

if (a[i] > a[0])

SwapTopk(&a[i], &a[0]);

TOPk(a, k,0);

for (int i &#61; 0; i < k; i&#43;&#43;)

for (int j &#61; 0; j < k-i; j&#43;&#43;)

if (a[j]

推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 原文地址http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/最开始时 ... [详细]
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • quartus管脚分配后需要保存吗_嵌入式必须会的一些硬件面试题,要试一试吗?你过来呀!...
    1、下面是一些基本的数字电路知识问题,请简要回答之。(1)什么是Setup和Hold时间?答:SetupHoldTime用于测试芯片对输入 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 用户视图(查看运行状态或其他参数)系统视图(配置设备的系统参数)system-viewEntersystemview,returnuservi ... [详细]
author-avatar
mbe5757086
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有